﻿'***************************************************************************\
' Module Name: SoundWindow.vb
' Project: SetDefaultAudioEndpoint http://sdae.codeplex.com/
' Copyright 2011 by jeff
' 
' This source is subject to the GNU General Public License version 2 (GPLv2).
' See http://www.gnu.org/licenses/gpl-2.0.html.
' All other rights reserved.
' 
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
' WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
'***************************************************************************/

Imports System.Windows.Automation

Namespace Windows7

  Class SoundWindow
    Implements IWindowsSoundImplementation

    Private Shared myWindow As AutomationElement
    Private ReadOnly myWindowWasAlreadyOpen As Boolean
    'Private myAudioEndpointTabControl As AudioEndpointTabControl
    Private myDisposedValue As Boolean     ' To detect redundant calls

    'Public Sub New()

    'End Sub

    Protected Sub New(ByVal window As AutomationElement, ByVal windowWasAlreadyOpen As Boolean)
      If (window Is Nothing) Then
        Throw New ArgumentNullException("window")
      End If

      SoundWindow.myWindow = window
      Me.myWindowWasAlreadyOpen = windowWasAlreadyOpen
    End Sub

    'Private ReadOnly Property WasAlreadyOpen() As Boolean
    '  Get
    '    Return Me.myWindowWasAlreadyOpen
    '  End Get
    'End Property

    Public Shared Function Open() As SoundWindow
      If (SoundWindow.Window IsNot Nothing) Then
        Throw New InvalidOperationException("The Sound control panel window is already open.")
      End If

      'assume the Sound control panel window is already open
      Dim soundWindowIsOpen As Boolean = True

      'use existing Sound window if it is open
      Dim aWindow As AutomationElement = FindFirstSoundWindow()

      'Sound control panel window is not open, so open it
      If (aWindow Is Nothing) Then
        'Sound control panel window was not already open
        soundWindowIsOpen = False

        aWindow = SoundWindow.OpenSoundControlPanelWindow
      End If

      Return New SoundWindow(aWindow, soundWindowIsOpen)
    End Function

    Public Function PlaybackEndpoints() As IAudioEndpointList Implements IWindowsSoundImplementation.PlaybackEndpoints
      Return SoundWindow.PlaybackTab.Endpoints
    End Function

    Public Function RecordingEndpoints() As IAudioEndpointList Implements IWindowsSoundImplementation.RecordingEndpoints
      Return SoundWindow.RecordingTab.Endpoints
    End Function

    Public Sub SetDefaultPlaybackEndpoint(ByVal endpoint As IAudioEndpoint) Implements IWindowsSoundImplementation.SetDefaultPlaybackEndpoint
      If (endpoint Is Nothing) Then
        Throw New ArgumentNullException("endpoint")
      End If

      SoundWindow.PlaybackTab.SetDefaultAudioEndpoint(endpoint)
    End Sub

    Public Sub SetDefaultRecordingEndpoint(ByVal endpoint As IAudioEndpoint) Implements IWindowsSoundImplementation.SetDefaultRecordingEndpoint
      If (endpoint Is Nothing) Then
        Throw New ArgumentNullException("endpoint")
      End If

      SoundWindow.RecordingTab.SetDefaultAudioEndpoint(endpoint)
    End Sub

    Private Shared ReadOnly Property Window() As AutomationElement
      Get
        Return SoundWindow.myWindow
      End Get
    End Property

    Private Shared Function OpenSoundControlPanelWindow() As AutomationElement

      'this doesn't work: window process seems to enter idle state
      ' before the actual AutomationElement window can be found
      'Dim windowProcess = Process.Start("control", "mmsys.cpl,,0")
      'Dim idleState As Boolean = windowProcess.WaitForInputIdle(5000)

      SoundWindow.StartControlPanelProcess()

      Dim result As AutomationElement = Nothing

      If (Not SoundWindow.TryFindSoundWindow(result)) Then
        Throw New ElementNotAvailableException("Could not open Sound control panel window.")
      End If

      Return result
    End Function

    Private Shared Function TryFindSoundWindow(ByRef window As AutomationElement) As Boolean
      'calculate the end time to wait for the Sound control panel window to be found
      ' by adding a certain number of milliseconds to the current time
      Dim waitForEndTime As Date = Date.Now.AddMilliseconds(My.Settings.TryFindSoundWindowTimeoutMilliseconds)

      Do
        window = FindFirstSoundWindow()
      Loop Until (window IsNot Nothing) OrElse (Date.Now > waitForEndTime)

      Return window IsNot Nothing
    End Function

    Private Shared Function FindFirstSoundWindow() As AutomationElement
      Return AutomationElement.RootElement.FindFirstInChildrenByName("Sound")
    End Function

    Private Shared Sub StartControlPanelProcess()
      System.Diagnostics.Process.Start("control", "mmsys.cpl,,0")
    End Sub

    Private Shared Function PlaybackTab() As AudioEndpointTabControl
      Return SoundWindow.TabControl("Playback")
    End Function

    Private Shared Function RecordingTab() As AudioEndpointTabControl
      Return SoundWindow.TabControl("Recording")
    End Function

    Private Shared ReadOnly Property TabControl(ByVal name As String) As AudioEndpointTabControl
      Get
        Dim result As AutomationElement = SoundWindow.TabCollectionControl.FindFirstInChildrenByName(name)

        If (result Is Nothing) Then
          Throw New ElementNotAvailableException(String.Format(Globalization.CultureInfo.CurrentCulture, "Could not find {0} tab on Sound control panel window.", name))
        End If

        Return New AudioEndpointTabControl(result)
      End Get
    End Property

    Private Shared Function TabCollectionControl() As AutomationElement
      Const TabControlAutomationId As String = "12320"

      If (SoundWindow.Window Is Nothing) Then
        Throw New InvalidOperationException("The Sound control panel window is not open.")
      End If

      Dim result As AutomationElement = SoundWindow.Window.FindFirstInChildrenByAutomationId(TabControlAutomationId)

      If (result Is Nothing) Then
        Throw New ElementNotAvailableException("Could not find tab controls on Sound control panel window.")
      End If

      Return result
    End Function

    Private Shared Sub Close()
      If (SoundWindow.Window Is Nothing) Then
        Throw New InvalidOperationException("The Sound control panel window is not open.")
      End If

      SoundWindow.OkButtonControl.ClickElement()
    End Sub

    Private Shared Function OkButtonControl() As AutomationElement
      Const WindowOkButtonAutomationId As String = "1"

      Dim result As AutomationElement = SoundWindow.Window.FindFirstInChildrenByAutomationId(WindowOkButtonAutomationId)

      If (result Is Nothing) Then
        Throw New ElementNotAvailableException("Could not find OK button on Sound control panel window.")
      End If

      Return result
    End Function

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
      If Not Me.myDisposedValue Then
        If disposing Then
          If ((SoundWindow.Window IsNot Nothing) AndAlso (Not Me.myWindowWasAlreadyOpen)) Then
            SoundWindow.Close()
          End If
        End If

        SoundWindow.myWindow = Nothing
      End If
      Me.myDisposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
      ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
      Dispose(True)
      GC.SuppressFinalize(Me)
    End Sub
#End Region

  End Class
End Namespace